package com.hxx.sbcommon.common.io.http.tools;


import com.hxx.sbcommon.common.io.http.ApacheHttpClientSimpleUseful;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.pool.PoolStats;
import org.apache.http.ssl.SSLContextBuilder;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.security.KeyStore;
import java.text.MessageFormat;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * ApacheHttpClient工具类
 */
@Slf4j
public class HttpClientTools {
    /**
     * 获取ContentType
     *
     * @param contentType application/json
     * @param charSet     utf-8
     * @return
     */
    public static String getContentType(String contentType, String charSet) {
        return MessageFormat.format("{0};charset={1}", contentType, charSet);
    }

    /**
     * 创建 Http 驱动
     *
     * @param poolMaxTotal             连接池里的最大连接数
     * @param poolDefaultMaxPerRoute   某一个/每服务每次能并行接收的请求数量
     * @param maxIdleSecond            连接空闲时间
     * @param connectionRequestTimeout 请求超时时间
     * @param connectTimeout           从连接池里获取连接超时时间
     * @param socketTimeout            读超时时间
     * @return
     */
    public static CloseableHttpClient createHttpClient(
            int poolMaxTotal, int poolDefaultMaxPerRoute, int maxIdleSecond, int connectionRequestTimeout,
            int connectTimeout, int socketTimeout,
            String proxyHost, int proxyPort) {
        RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder.create();
        ConnectionSocketFactory plainSF = new PlainConnectionSocketFactory();
        registryBuilder.register("http", plainSF);
        // 指定信任密钥存储对象和连接套接字工厂
        try {
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            // 信任任何链接
            TrustStrategy anyTrustStrategy = (x509Certificates, s) -> true;
            SSLContext sslContext = SSLContextBuilder.create()
                    .setProtocol("TLS")// TLS TLSv1.2
                    .loadTrustMaterial(trustStore, anyTrustStrategy)
                    .build();
            LayeredConnectionSocketFactory sslSF = new SSLConnectionSocketFactory(sslContext,
                    (hostname, session) -> true);
            registryBuilder.register("https", sslSF);
        } catch (Exception e) {
            log.error("CreateHttpClientException", e);
            throw new RuntimeException(e);
        }

        Registry<ConnectionSocketFactory> registry = registryBuilder.build();
        //设置连接管理器
        PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(registry);
        // 所有路由允许的最大连接数.
        connManager.setMaxTotal(poolMaxTotal);
        // 未通过调用 setMaxPerRoute 指定的路由所允许的最大连接数
        connManager.setDefaultMaxPerRoute(poolDefaultMaxPerRoute);

        // 打印池中信息
        startIdleAndExpiredConnectionMonitor(connManager);

        RequestConfig requestConfig = RequestConfig.custom()
                // 在抛出异常之前尝试从连接池签出连接时要等待多长时间(例如，如果所有连接都被签出，则连接池不会立即返回).
                .setConnectionRequestTimeout(connectionRequestTimeout)
                // 在抛出超时异常之前等待与远程服务器建立连接的时间
                .setConnectTimeout(connectTimeout)
                // 在抛出超时异常之前等待服务器响应各种调用的时间.
                .setSocketTimeout(socketTimeout)
                .build();

        // 代理
        DefaultProxyRoutePlanner routePlanner = null;
        if (!StringUtils.isBlank(proxyHost)) {
            HttpHost proxy = new HttpHost(proxyHost, proxyPort, "http");
            routePlanner = new DefaultProxyRoutePlanner(proxy);
        }

        ConnectionKeepAliveStrategy keepAliveStrategy = DefaultConnectionKeepAliveStrategy.INSTANCE;

        return HttpClientBuilder.create()
                // 代理
                .setRoutePlanner(routePlanner)
                // 连接池
                .setConnectionManager(connManager)
                // keepalive
                .setKeepAliveStrategy(keepAliveStrategy)
                .setDefaultRequestConfig(requestConfig)
                // 驱逐闲置连接
                .evictExpiredConnections()
                .evictIdleConnections(maxIdleSecond, TimeUnit.SECONDS)
                .build();
    }

    /**
     * 执行HTTP请求
     *
     * @param client
     * @param url
     * @param request
     * @param responseConsumer
     * @return
     * @throws IOException
     */
    public static ApacheHttpClientSimpleUseful.HttpApiResp execute(
            CloseableHttpClient client, String url, HttpUriRequest request,
            Consumer<HttpResponse> responseConsumer) throws IOException {
        // 发起请求
        CloseableHttpResponse response = client.execute(request);
        if (responseConsumer != null) responseConsumer.accept(response);

        return new ApacheHttpClientSimpleUseful.HttpApiResp(url, response, true);
    }

    /**
     * 执行 http 请求
     *
     * @param client
     * @param url
     * @param request
     * @param header
     * @return
     * @throws IOException
     */
    public static ApacheHttpClientSimpleUseful.HttpApiResp execute(
            CloseableHttpClient client, String url, HttpUriRequest request,
            Map<String, String> header) throws IOException {
        // 处理header
        if (header != null) {
            for (Map.Entry<String, String> entry : header.entrySet()) {
                request.setHeader(entry.getKey(), entry.getValue());
            }
        }
        Header headerConnection = request.getFirstHeader("Connection");
        if (headerConnection == null) {
            request.addHeader("Connection", "close");
        }
        // 发起请求
        return execute(client, url, request, (Consumer<HttpResponse>) null);
    }

    /**
     * 启动 清理连接池空闲&失效连接 的监控
     */
    private static void startIdleAndExpiredConnectionMonitor(PoolingHttpClientConnectionManager connManager) {
        Thread idleConnectionMonitor = new Thread(() -> {
            try {
                while (!Thread.currentThread().isInterrupted()) {
                    Thread.sleep(5000L); // 每5秒检查一次
                    PoolStats totalStats = connManager.getTotalStats();
                    log.info("http连接池状态: [在使用连接数: {}; 可用连接数: {}; 等待中的请求数: {}; 最大连接数: {}]",
                            totalStats.getLeased(), totalStats.getAvailable(), totalStats.getPending(), totalStats.getMax());
//                    connManager.closeExpiredConnections(); // 关闭过期(失效)连接
//                    connManager.closeIdleConnections(90, TimeUnit.SECONDS); // 关闭空闲90秒的连接
                }
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }, "IdleConnectionMonitorThread");
        // 设置为守护线程（不影响应用关闭）
        idleConnectionMonitor.setDaemon(true);
        idleConnectionMonitor.start();
    }
}
